using System.Collections.Generic;

namespace StackCalc.Instructions
{
	public class Operator : IInstruction
	{
		public Operator(OpCode opCode)
		{
			Code = opCode;
		}

		public OpCode Code { get; private set; }

		public void Execute(List<IInstruction> stack, StackEngine stackEngine)
		{
			INumericInstruction firstArgument = stack.PeekInstructionFromTop(2) as INumericInstruction;
			INumericInstruction secondArgument = stack.PeekInstructionFromTop(1) as INumericInstruction;

			if (Code == OpCode.Eq)
			{
				// eq also allows braced expressions to be compared
				BracedExpression firstBracedExpression = stack.PeekInstructionFromTop(2) as BracedExpression;
				BracedExpression secondBracedExpression = stack.PeekInstructionFromTop(1) as BracedExpression;

				if (firstArgument != null && secondArgument != null)
				{
					// remove the two uppermost instructions
					stack.PopInstructionFromTop(1);
					stack.PopInstructionFromTop(1);

					if (firstArgument.NumericValue == secondArgument.NumericValue)
					{
						// push true
						stack.Add(new PushInteger(0));
					}
					else
					{
						// push false
						stack.Add(new PushInteger(1));
					}
				}

				if (firstBracedExpression != null && secondBracedExpression != null)
				{
					// remove the two uppermost instructions
					stack.PopInstructionFromTop(1);
					stack.PopInstructionFromTop(1);

					if (firstBracedExpression.StoredValue == secondBracedExpression.StoredValue)
					{
						// push true
						stack.Add(new PushInteger(0));
					}
					else
					{
						// push false
						stack.Add(new PushInteger(1));
					}
				}

				return;
			}

			if (firstArgument != null && secondArgument != null)
			{
				// remove the two uppermost instructions
				stack.PopInstructionFromTop(1);
				stack.PopInstructionFromTop(1);

				// perform arithmetic operations
				switch (Code)
				{
					case OpCode.Add:
						stack.Add(new PushInteger(firstArgument.NumericValue + secondArgument.NumericValue));
						break;

					case OpCode.Sub:
						stack.Add(new PushInteger(firstArgument.NumericValue - secondArgument.NumericValue));
						break;

					case OpCode.Mul:
						stack.Add(new PushInteger(firstArgument.NumericValue*secondArgument.NumericValue));
						break;

					case OpCode.Div:
						stack.Add(new PushInteger(firstArgument.NumericValue/secondArgument.NumericValue));
						break;

					case OpCode.Mod:
						stack.Add(new PushInteger(firstArgument.NumericValue%secondArgument.NumericValue));
						break;

					case OpCode.And:
						if (firstArgument.NumericValue == 1 && secondArgument.NumericValue == 1)
						{
							stack.Add(new PushInteger(1));
						}
						else
						{
							stack.Add(new PushInteger(0));
						}
						break;

					case OpCode.Or:
						if (firstArgument.NumericValue == 0 && secondArgument.NumericValue == 0)
						{
							stack.Add(new PushInteger(0));
						}
						else
						{
							stack.Add(new PushInteger(1));
						}
						break;

					case OpCode.Lt:
						if (firstArgument.NumericValue < secondArgument.NumericValue)
						{
							stack.Add(new PushInteger(0));
						}
						else
						{
							stack.Add(new PushInteger(1));
						}
						break;

					case OpCode.Gt:
						if (firstArgument.NumericValue > secondArgument.NumericValue)
						{
							stack.Add(new PushInteger(0));
						}
						else
						{
							stack.Add(new PushInteger(1));
						}
						break;
				}
			}
		}

		public string Validate(List<IInstruction> stack)
		{
			if (stack.Count < 2)
			{
				return "Not enough elements on stack!";
			}

			// get arguments, should be numeric
			INumericInstruction firstArgument = stack.PeekInstructionFromTop(2) as INumericInstruction;
			INumericInstruction secondArgument = stack.PeekInstructionFromTop(1) as INumericInstruction;

			if (Code == OpCode.Eq)
			{
				// eq also allows braced expressions to be compared
				BracedExpression firstBracedExpression = stack.PeekInstructionFromTop(2) as BracedExpression;
				BracedExpression secondBracedExpression = stack.PeekInstructionFromTop(1) as BracedExpression;

				if ((firstArgument == null && secondArgument != null) ||
				    (firstArgument != null && secondArgument == null) ||
				    (firstBracedExpression == null && secondBracedExpression != null) ||
				    (firstBracedExpression != null && secondBracedExpression == null))
				{
					return "Arguments not comparable!";
				}

				return string.Empty;
			}

			if (firstArgument == null || secondArgument == null)
			{
				return "Argument not numeric!";
			}

			// determine special operator based errors
			switch (Code)
			{
				case OpCode.Div:
				case OpCode.Mod:

					if (secondArgument.NumericValue == 0)
					{
						return "Second argument zero!";
					}

					break;

				case OpCode.Or:
				case OpCode.And:

					if (firstArgument.NumericValue < 0 || firstArgument.NumericValue > 1 ||
					    secondArgument.NumericValue < 0 || secondArgument.NumericValue > 1)
					{
						return "Operator not boolean!";
					}

					break;
			}

			return string.Empty;
		}
	}

	public enum OpCode
	{
		Add,
		Sub,
		Mul,
		Div,
		Mod,
		And,
		Or,
		Eq,
		Lt,
		Gt
	}
}
